package com.rem.chat.websocket;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.rem.chat.common.utils.JacksonUtil;
import com.rem.chat.common.utils.RedisComponent;
import com.rem.chat.mapper.ChatMessageMapper;
import com.rem.chat.mapper.ChatSessionUserMapper;
import com.rem.chat.mapper.UserContactApplyMapper;
import com.rem.chat.mapper.UserInfoMapper;
import com.rem.chat.pojo.dataobject.ChatMessageDO;
import com.rem.chat.pojo.dataobject.UserContactApplyDO;
import com.rem.chat.pojo.dataobject.UserInfoDO;
import com.rem.chat.service.dto.MessageSendDTO;
import com.rem.chat.service.dto.UserSessionChatDTO;
import com.rem.chat.service.dto.WSInitDataDTO;
import com.rem.chat.service.enums.MessageTypeEnum;
import com.rem.chat.service.enums.UserContactTypeEnum;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author: Themberfue
 * @date: 2025/6/14 15:23
 * @description:
 */
@Component
public class ChannelContextUtil {
    public static final ConcurrentHashMap<String, Channel> USER_CONTEXT_MAP = new ConcurrentHashMap<>();
    public static final ConcurrentHashMap<String, ChannelGroup> GROUP_CONTEXT_MAP = new ConcurrentHashMap<>();

    @Resource
    RedisComponent redisComponent;

    @Resource
    UserInfoMapper userInfoMapper;

    @Resource
    ChatSessionUserMapper chatSessionUserMapper;

    @Resource
    ChatMessageMapper chatMessageMapper;

    @Resource
    UserContactApplyMapper userContactApplyMapper;

    /**
     * 添加用户连接到服务器
     * @param userId 用户id
     * @param channel 用户通道
     */
    public void addContext(String userId, Channel channel) {
        // Step 1. 获取通道id
        String channelId = channel.id().toString();
        AttributeKey<String> attributeKey;
        if (!AttributeKey.exists(channelId)) {
            attributeKey = AttributeKey.newInstance(channelId);
        } else {
            attributeKey = AttributeKey.valueOf(channelId);
        }

        // Step 2. 将通道与用户id进行绑定
        channel.attr(attributeKey).set(userId);

        // Step 3. 将用户添加到对应的群聊通道组里
        List<String> contactList = redisComponent.getUserContactList(userId);
        if (!contactList.isEmpty()) {
            for (String groupId: contactList) {
                if (groupId.startsWith(UserContactTypeEnum.GROUP.getPrefix())) {
                    add2Group(groupId, channel);
                }
            }
        }
        USER_CONTEXT_MAP.put(userId, channel);

        // ? 获取上次离线的时间
        UserInfoDO userInfo = userInfoMapper.selectById(userId);
        Long lastOffTime = userInfo.getLastOffTime();

        // Step 4. 查询会话信息
        List<UserSessionChatDTO> userSessionList = chatSessionUserMapper.selectUserSessionChat(userId);
        WSInitDataDTO wsInitDataDTO = new WSInitDataDTO();
        wsInitDataDTO.setChatSessionUserList(userSessionList);

        // Step 5. 查询聊天消息（你收到的消息）
        // ! 聊天信息就是你加入的群组以及你自己，自己作为接收人接收别人发来的消息
        contactList = contactList.stream()
                .filter(contact -> contact.startsWith(UserContactTypeEnum.GROUP.getPrefix()))
                .collect(Collectors.toCollection(ArrayList::new));
        contactList.add(userId);

        QueryWrapper<ChatMessageDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .ge(ChatMessageDO::getSendTime, lastOffTime)
                .in(ChatMessageDO::getContactId, contactList);
        List<ChatMessageDO> chatMessageList = chatMessageMapper.selectList(queryWrapper);
        wsInitDataDTO.setChatMessageList(chatMessageList);

        // Step 6. 查询好友申请信息
        QueryWrapper<UserContactApplyDO> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.lambda()
                .eq(UserContactApplyDO::getReceiveUserId, userId)
                .ge(UserContactApplyDO::getLastApplyTime, lastOffTime);
        Long applyCount = userContactApplyMapper.selectCount(queryWrapper1);
        wsInitDataDTO.setApplyCount(applyCount);

        // Step 7. 发送消息
        MessageSendDTO<Object> messageSendDTO = MessageSendDTO.builder()
                .messageType(MessageTypeEnum.INIT.getType())
                .contactId(userId)
                .extendData(wsInitDataDTO)
                .build();
        sendMessage(messageSendDTO, userId);
    }

    /**
     * 添加用户通道到群组通道
     * @param groupId 群组id
     * @param userId 用户id
     */
    public void addUser2Group(String groupId, String userId) {
        Channel userChannel = USER_CONTEXT_MAP.get(userId);
        add2Group(groupId, userChannel);
    }

    /**
     * 添加用户通道到群组通道
     * @param groupId 群组id
     * @param channel 用户通道
     */
    private void add2Group(String groupId, Channel channel) {
        ChannelGroup channelGroup = GROUP_CONTEXT_MAP.get(groupId);
        if (null == channelGroup) {
            channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            GROUP_CONTEXT_MAP.put(groupId, channelGroup);
        }
        if (null == channel) {
            return;
        }
        channelGroup.add(channel);
    }

    /**
     * 移除用户通道
     * @param userId 用户id
     */
    public void removeContext(String userId) {
        if (!StringUtils.hasText(userId)) {
            return;
        }
        Channel userChannel = USER_CONTEXT_MAP.get(userId);
        removeContext(userChannel);
    }

    /**
     * 移除用户通道
     * @param channel 用户通道
     */
    public void removeContext(Channel channel) {
        if (null == channel) return;

        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
        // Step 1. 将用户从用户会话组里删除
        USER_CONTEXT_MAP.remove(userId);

        // Step 2. 将缓存中的用户token删除
        redisComponent.removeUserToken(userId);

        // Step 3. 将用户心跳包删除
        redisComponent.removeUserHeartBeat(userId);

        // Step 4. 更新用户最后离开时间
        UpdateWrapper<UserInfoDO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(UserInfoDO::getUserId, userId)
                .set(UserInfoDO::getLastOffTime, System.currentTimeMillis());
        userInfoMapper.update(updateWrapper);

        channel.close();
    }

    /**
     * 发送消息到客户端
     * @param messageSendDTO 发送消息
     */
    public void sendMessage(MessageSendDTO<Object> messageSendDTO) {
        UserContactTypeEnum typeEnum = UserContactTypeEnum.getByPrefix(messageSendDTO.getContactId());
        if (null == typeEnum) {
            return;
        }
        switch (typeEnum) {
            case USER:
                send2User(messageSendDTO);
                break;
            case GROUP:
                send2Group(messageSendDTO);
                break;
        }
    }

    private void send2User(MessageSendDTO<Object> messageSendDTO) {
        // Step 1. 校验接收人是否存在
        if (!StringUtils.hasText(messageSendDTO.getContactId())) {
            return;
        }
        // Step 2. 发送消息到接收人客户端
        sendMessage(messageSendDTO, messageSendDTO.getContactId());

        // Step 3. 处理强制下线消息的情况
        if (MessageTypeEnum.FORCE_OFF_LINE.getType()
                .equals(messageSendDTO.getMessageType())) {
            Channel channel = USER_CONTEXT_MAP.get(messageSendDTO.getSendUserId());
            if (null == channel) {
                return;
            }
            channel.close();
        }
    }

    /**
     * 发送消息到客户端
     * @param messageSendDTO 发送消息
     * @param receiveId 接收人id
     */
    private void sendMessage(MessageSendDTO<Object> messageSendDTO, String receiveId) {
        if (!StringUtils.hasText(receiveId)) {
            return;
        }
        Channel sendChannel = USER_CONTEXT_MAP.get(receiveId);
        if (null == sendChannel) {
            return;
        }

        if (MessageTypeEnum.ADD_FRIEND_SELF.getType()
                .equals(messageSendDTO.getMessageType())) {
            UserInfoDO userInfoDO = (UserInfoDO) messageSendDTO.getExtendData();
            messageSendDTO.setMessageType(MessageTypeEnum.ADD_FRIEND.getType());
            messageSendDTO.setContactId(userInfoDO.getUserId());
            messageSendDTO.setContactName(userInfoDO.getNickName());
            messageSendDTO.setExtendData(null);
        } else {
            // ? 相当于客户端而言，联系人就是发送人，所以这里转一下再发送
            messageSendDTO.setContactId(messageSendDTO.getSendUserId());
            messageSendDTO.setContactName(messageSendDTO.getSendUserNickName());
            // ? 用户 A 给用户 B 发送消息，此时发送者是 A，接收者是 B。
            // ? 服务器要把消息推送给 B，在 B 的客户端界面上，A 会显示为 B 的联系人
            // ? B 的客户端收到消息后，会把 A 显示为自己的联系人，并展示 A 发送的消息
        }

        sendChannel.writeAndFlush(new TextWebSocketFrame(
                JacksonUtil.writeValueAsString(messageSendDTO)));
    }

    private void send2Group(MessageSendDTO<Object> messageSendDTO) {
        // Step 1. 校验接收人是否存在
        if (!StringUtils.hasText(messageSendDTO.getContactId())) {
            return;
        }

        // Step 2. 发送消息到群组
        ChannelGroup channelGroup = GROUP_CONTEXT_MAP.get(messageSendDTO.getContactId());
        if (null == channelGroup) {
            return;
        }
        channelGroup.writeAndFlush(new TextWebSocketFrame(
                JacksonUtil.writeValueAsString(messageSendDTO)));

        // Step 3. 移除群聊
        MessageTypeEnum messageType = MessageTypeEnum.getByStatus(messageSendDTO.getMessageType());
        if (MessageTypeEnum.REMOVE_GROUP.equals(messageType) || MessageTypeEnum.LEAVE_GROUP.equals(messageType)) {
            String userId = (String) messageSendDTO.getExtendData();

            redisComponent.removeUserContact(userId, messageSendDTO.getContactId());
            Channel channel = USER_CONTEXT_MAP.get(userId);
            if (null == channel) {
                return;
            }
            channelGroup.remove(channel);
        }

        if (MessageTypeEnum.DISSOLUTION_GROUP.equals(messageType)) {
            GROUP_CONTEXT_MAP.remove(messageSendDTO.getContactId());
            channelGroup.close();
        }
    }
}
